import type { S3Client } from '@aws-sdk/client-s3';
import type { Connection } from 'amqplib';
import type { Enforcer } from 'casbin';
import type { Db } from 'mongodb';
import type { RedisClientType, RedisDefaultModules } from 'redis';
import { container } from 'tsyringe';
import type { DataSource, EntityManager, EntityTarget, ObjectLiteral, SelectQueryBuilder } from "typeorm";
import { CoreError } from "./error";

export class LoadPluginError extends CoreError<string> {
    constructor(msg: string) {
        super(500, 'LoadPluginError', msg)
    }
}

export class TestPluginError extends CoreError<string> {
    constructor(msg: string) {
        super(500, 'TestPluginError', msg)
    }
}

export interface Plugins {
    ['@sker/orm']: <T>(ds: DataSource) => Promise<T>
}
export interface HandlerDataSource<T> {
    (ds: DataSource): Promise<T>
}
export interface HandlerMqConnection<T> {
    (conn: Connection): Promise<T>;
}
export type PluginName = keyof Plugins;
export interface HasuraPayload<T> {
    variables?: T, query: string
}
export interface HasuraHandler<T> {
    (): Promise<HasuraPayload<T>>
}
export interface S3Handler<T> {
    (client: S3Client, config: any): Promise<T>;
}
export interface CasbinHandler<T> {
    (e: Enforcer): Promise<T>;
}
export interface MongoHandler<T> {
    (db: Db): Promise<T>;
}
export interface RedisHandler<T> {
    (client: RedisClientType<RedisDefaultModules>): Promise<T>;
}
export interface Addon<T> {
    (params: any): Promise<T>;
}
export interface AddonHandler<T> {
    (addon: Addon<T>): Promise<T>;
}
export async function usePlugin<T, K extends PluginName>(name: '@sker/addon', run: AddonHandler<T>, ...args: any[]): Promise<T>;
export async function usePlugin<T, K extends PluginName>(name: '@sker/redis', run: RedisHandler<T>, ...args: any[]): Promise<T>;
export async function usePlugin<T, K extends PluginName>(name: '@sker/orm', run: HandlerDataSource<T>, ...args: any[]): Promise<T>;
export async function usePlugin<T, K extends PluginName>(name: '@sker/mongo', run: MongoHandler<T>, ...args: any[]): Promise<T>;
export async function usePlugin<T, K extends PluginName>(name: '@sker/mq', run: HandlerMqConnection<T>, ...args: any[]): Promise<T>;
export async function usePlugin<T, K extends PluginName>(name: '@sker/hasura', run: HasuraHandler<T>, ...args: any[]): Promise<T>;
export async function usePlugin<T, K extends PluginName>(name: '@sker/s3', run: S3Handler<T>, ...args: any[]): Promise<T>;
export async function usePlugin<T, K extends PluginName>(name: '@sker/casbin', run: CasbinHandler<T>, ...args: any[]): Promise<T>;
export async function usePlugin(name: string, run: any, ...args: any[]) {
    const p = require(name)
    const { default: main } = p
    const result = await main(container, run, ...args)
    return result;
}
/**
 * 数据库
 */
export async function useDataSource<T>(run: HandlerDataSource<T>): Promise<T> {
    return usePlugin('@sker/orm', run)
}
export interface HandlerTransaction<T> {
    (manager: EntityManager): Promise<T>
}
/**
 * 事务
 */
export async function useTransaction<T>(run: HandlerTransaction<T>): Promise<T> {
    return useDataSource(async ds => {
        return ds.transaction((manager) => run(manager))
    })
}
/**
 * 实体管理器
 */
export async function useEntityManager<T>(run: HandlerTransaction<T>) {
    return useDataSource(async ds => {
        return run(ds.manager)
    })
}
/**
 * 查询
 */
export interface HandlerQueryBuilder<T extends ObjectLiteral, O> {
    (qb: SelectQueryBuilder<T>): Promise<O>
}
export async function useQueryBuilder<T extends ObjectLiteral, O>(target: EntityTarget<T>, alias: string, run: HandlerQueryBuilder<T, O>): Promise<O> {
    return useEntityManager(manager => {
        return run(manager.createQueryBuilder(target, alias))
    })
}
/**
 * query
 */
export async function useQuery<T>(sql: string, parameters: any[]): Promise<T> {
    return useEntityManager(manager => {
        return manager.query(sql, parameters)
    })
}
/**
 * query factory
 */
export function useQueryBuilderFactory<T extends ObjectLiteral, O>(run: HandlerQueryBuilder<T, O>) {
    return (qb: SelectQueryBuilder<T>): Promise<O> => {
        return run(qb)
    }
}
export async function usePluginTest<K extends PluginName>(name: K) {
    const { test } = require(name)
    const plugin = await test(container)
    return plugin;
}